博采 27 门语言之长,提升 Python 的能力
目录
拓宽我们的视野 过程式编程:C、Rust、Cython 面向对象的数据模型:Java、C#、Eiffel 面向对象的 C 派生:C++、D 面向数组的数据处理:MATLAB/Octave、Julia 统计数据分析:R 计算管道建模:Haskell、Scala、Clojure、F# 事件驱动编程:JavaScript、Go、Erlang、Elixir 渐变类型:TypeScript 动态元编程:Hy、Ruby 务实问题解决:Lua、PHP、Perl 编程思维:Scratch、Logo
作为世界上最流行的编程语言之一的共同设计者,我经常看到一个令人沮丧的行为(在 Python 社区和其它社区):有影响力者试图激发人们对“败给”其它开源社区的恐惧,从而调动人们对社区作贡献的积极性。(我自己偶尔也会犯这种错误,这让我更容易发现其他人是否也落入了同样的陷阱)。
虽然学习其它编程语言社区的经验是件好事,但基于恐惧的方法来激励行动是有严重问题的,因为这会刺激本社区成员将其它社区的人视为争夺开源贡献者关注的敌人,而不是作为在更大挑战中的潜在盟友(推动软件开发艺术发展)。这还会告诉那些喜欢其它语言的人,在一个把他们以及他们的同伴视为“敌对竞争对手”的社区里,他们是不受欢迎的。
事实上,我们希望有多种多样的跨平台的开源编程语言供选择,因为编程语言是思考的首要工具——使我们能够以明确的方式表达我们的想法,从而让计算机也能够理解。如果有人找到了一种适合大脑的语言,能够解决眼前的问题,那就太好了,不管他们选择的是哪种(些)语言。
因此,我对 Python 社区有三个具体的请求,以及一个较为宽泛的建议。首先,我的请求是:
如果我们要利用社区的本能来激励行动,就应该避免利用恐惧感,而应该利用自豪感。 当我们将恐惧作为激励因素时,就像在说“如果我们不做 X,就会失去开发者对 Python 的关注”,这等于是故意地在自由的开源贡献者中创造悲观的情绪。然而,依赖社区的自豪感就像在说“目前尚不清楚如何在 Python 中解决 X 问题。如果我们看看 Y 语言,就可以看到他们有一个非常好的方法来解决问题 X,我们可以吸收进 Python,以提供类似的舒适的用户体验。”积极的态度让我们对自己的努力感到自豪,而不是贬低他人的努力,这有助于在 Python 社区内促成一种持续学习的文化,并促进与其它社区改善协作关系,共同发展。
克制对其它开源编程语言社区采取轻蔑的态度,尤其当这些社区授权人们解决自己的问题,而不是等待商业软件供应商来解决问题。 世界上大多数重要的问题解决起来都是无利可图的(因为受苦于这些问题的人并不富裕,而且无法左右机构基金的决定),所以我们应该鼓励试图解决这些问题的人,不管我们如何看待他们的技术选择。
如果我们认识的人刚开始学习编程,并且他们选了一种我们不喜欢的语言,我们应该支持他们的选择。 他们比我们更知道什么适合自己,适合我们的语言不一定适合他们。如果他们对自己最初的选择感到了沮丧,甚至已经对学习编程变得没有动力,此时再给他们推荐别的编程语言。这个建议还适用于那些在改善糟糕的网络安全状况的人:我们在面对天生不安全的语言时,采取的方法是改进操作系统的沙箱功能,逐步学习有更好的本地安全属性的语言,并改善现有语言的默认行为,而不是列举为什么从程序安全性的角度来看,他们选择的语言是一个糟糕的选择,来迷惑初学者。(如果有人部署了由初学者编写的未经审核的软件来处理安全敏感的任务,那就不是开发者的问题,而且部署者的问题,他们没有对软件的出处和安全属性进行适当的尽职调查。)
我的宽泛的建议针对那些遇到了 Python 核心程序的限制,并因此希望探索 Python 中可用的“思考工具”的人。这个建议就是:
拓宽我们的视野
在开发 Python 核心程序的过程中,我们会做的一件事是查看其它语言中解决了我们正面临的问题的特性,看看是否有办法既吸收它们,又使 Python 代码更易于阅读和编写。这意味着学习其它专注于特定软件开发风格的编程语言,可以帮助我们在使用 Python 时,提高对这种编程风格的理解。
为了提供帮助,我在下面列出了一些值得探索的领域,以及可能加深对这些领域的理解的语言。我尽可能链接了维基百科的页面,而不是直接链接到语言的主页,因为维基百科经常会提供有趣的历史背景,当你为了教育目的学习一门新的编程语言,而不是直接用于实际应用时,这些背景值得去了解。
虽然我知道这些语言中的大部分(并且在开发生产系统时使用过几种),但这份推荐清单中还包括我间接知道的语言(通常是通过阅读教程和设计文档,或者通过与我信任的人交谈,以获取对一门语言的优点与缺陷的洞察)。
还有很多本应该放但没有放进名单里的语言语言,所以下面罗列的仅是我感兴趣的部分(例如,我主要感兴趣的是 Linux、Android 和 Windows 的生态系统,所以我舍弃了 Apple 生态中的 Objective-C 和 Swift 语言,另外我也不熟悉 Processing 这种专注于艺术的编程语言,无法想象学习它们能教给 Python 开发者什么)。
除了考虑一门语言可能教给你的东西,如果你想获得一份更全面的清单,可以去查看 IEEE Spectrum 关于编程语言流行度和增长度的年度榜单。
过程式编程:C、Rust、Cython
Python 默认的执行模型是过程式的:从主模块的顶部开始,逐条语句地执行。Python 对下面介绍的所有数据和编程建模方法的支持,都建立在这种过程式的执行模型上。
C 语言仍然是无可争议的底层过程式编程的统治者。它是 Python 官方解释器以及 Linux 操作系统内核的核心实现语言。作为一名软件开发人员,学习 C 语言是更多地了解底层硬件的最好方法之一——C 语言经常被称为“可移植的汇编语言”,对于任何新的 CPU 架构来说,第一个交叉编译的应用程序将是 C 编译器。
Rust 是一种相对较新的编程语言,由 Mozilla 创造。Rust 的目标是吸取整个行业在不使用 C 时遇到的所有教训,设计一门能与 C 库相互操作的新语言,提供底层的系统编程所需的对硬件用途的精确控制,但使用不同的编译方法来进行数据建模和内存管理,从结构上消除许多困扰 C 程序的常见缺陷(如缓冲区溢出、指针重复释放错误、空指针访问和线程同步问题)。经过培训和早期的专业经验,我是一名嵌入式系统工程师,而 Rust 是我见过的第一种看起来有潜力缩减当前由 C 语言和自定义汇编代码所主导的生态位的新语言。
Cython 也是一种较底层的过程式语言,但与 C 和 Rust 等通用语言不同,Cython 专门用于编写 CPython 的扩展模块。为了实现这一目标,Cython 被设计为 Python 的超集,允许程序员选择何时支持纯 Python 语法以获得灵活性,何时支持 Cython 的语法扩展,以便生成在速度和内存效率方面能与原生 C 代码相当的代码。
学习这些语言,你可以加深在内存管理、算法效率、二进制接口(ABI)兼容性、软件可移植性、以及将源代码转换为运行系统等实践方面的见解。
面向对象的数据模型:Java、C#、Eiffel
编程最主要做的事情之一是为现实世界建模,最流行的做法是提供原生的语法支持面向对象编程:对数据作结构化的分组,使用类方法操作那些数据结构。
Python 本身是经过精心设计的,无需先编写自己的类就可以使用面向对象的特性。并不是每种语言都采用这种方法——本小节中列出的语言都认为学习面向对象设计是使用该语言的必要条件。
在 20 世纪 90 年代中后期,Sun Microsystems 公司进行了一次大规模的市场推广,Java 成为了许多高等院校中计算机科学入门课的默认语言。虽然如今在许多教学活动中,Java 已经被 Python 所取代,但它仍然是开发商用程序时最流行的语言之一。还有一些基于通用 JVM(Java 虚拟机)运行时的语言,例如 Python 的 Jython 实现。Android 系统的 Dalvik 和 ART 环境则是基于 Java 开放的 API 二次开发。
C# 在许多方面与 Java 相似,在 Sun 和微软未能解决他们关于微软的 Java 实现(即 J++)的业务差异之后,C# 成为了一种替代方案。像 Java 一样,这是一门开发商用程序的流行语言,还有其它一些语言共享着 .NET CLR(公共语言运行时),包括 Python 的 IronPython 实现 (最早的 IronPython 1.0 的核心组件被提取成了与语言无关的 .NET 动态语言运行库)。在很长一段时间里,. NET 是一种专用于 Windows 的技术,而 mono 作为一种跨平台的开源实现,但微软在 2015 年初转向了开源生态系统战略。
与此清单中的大多数语言不同,我不推荐在日常工作中使用 Eiffel。但是,我依然推荐学习它,因为它教会了我许多关于良好的面向对象设计的知识,比如它认为“可验证的正确性”是应用程序的设计目标。(学习 Eiffel 也让我明白了为什么“可验证的正确性”并不是大多数软件开发时的设计目标,因为可验证的正确软件实在不能很好地处理模糊性,并且完全不适用于那些你不清晰相关的约束条件却需要给自己留下足够的回旋余地,以便能够通过迭代开发找出更具体的细节的情况。)
学习这些语言,你可以深入了解继承模型、契约式设计、类不变性(class invariant)、前置条件、后置条件、协方差、逆变、类方法解析顺序、泛型编程以及其它适用于 Python 类型系统的概念。还有很多标准库模块和第三方框架使用这种“看得见的面向对象”的设计风格,比如 unittest 和 logging 模块,以及 Django 框架中基于类的视图。
面向对象的 C 派生:C++、D
CPython 运行环境可以被视为一个“带有对象的 C”的编程环境——在其核心,CPython 使用 C 的方法实现面向对象编程,即定义 C 结构体来保存相关的数据,并将结构体的实例作为第一个参数传递给函数,然后对数据进行操作(这就是 CPython C API 中全能的 PyObject * 指针)。这种设计模式对应到 Python 层面,就是实例方法的显式 self 参数以及类方法的显式 cls 参数。
C++ 的目标是保持与 C 语言源代码的完全兼容,同时添加更高级的特性,例如支持原生的面向对象编程和基于模板的元编程。它是出了名的冗长和难以编程(尽管 2011 年对语言标准的更新解决了许多糟糕的问题),但它也是许多领域的编程首选,包括 3D 建模的图形化引擎和跨平台应用的开发框架(例如 Qt)。
D 语言也很有趣,因为它与 C++ 的关系类似于 Rust 与 C 的关系:它的目标是保留 C++ 的大多数令人满意的特性,同时也避免它的许多问题(如缺乏内存安全)。不像 Rust,D 不是一种从头开始设计的新编程语言——恰恰相反,D 是 C++ 的衍生物,虽然它不像 C++ 一样是一个严格的 C 超集,但它遵循着一个设计原则,即任何落入 C 和 D 的共同子集的代码,在两种语言中必须要表现一致。
学习这些语言,你可以更深入地了解将高级语言的特性与底层 C 运行时模型相结合的复杂性。学习 C++,在 Python 中操作用 C++ 编写的库和工具包时,也可能会有帮助。
面向数组的数据处理:MATLAB/Octave、Julia
面向数组的编程是为了支持数值编程模型:那些基于矩阵代数和相关数值方法的模型。
虽然 Python 的标准库不直接支持这一点,但 Python 在设计时考虑了面向数组的编程,并专门为第三方 NumPy 库和类似的面向数组的工具添加了一系列语法和语义特性。
在许多方面,Python 的科学技术栈 被作为商业 MATLAB 的替代方案,后者被广泛用于科学和工程领域的建模、仿真和数据分析。GNU Octave 是一个开源的方案,目标是兼容 MATLAB 代码的语法,允许人们对照这两种面向数组的编程方法。
Julia 是另一种相对较新的语言,重点关注面向数组的编程和基于类型的函数重载。
学习这些语言,你可以了解 Python 的科学技术栈,以及有机会通过像 OpenCL 和 Nvidia 的 CUDA 这种技术来探索硬件层面的并行执行,并通过 Apache Spark 和专用于 Python 的 Blaze 来了解分布式数据处理。
统计数据分析:R
随着对大型数据集的接触越来越多,对灵活处理这些数据集的分析工具的需求也越来越大。R 编程语言就是这样的工具,它特别关注统计性的数据分析和可视化。
学习 R 能会让你深入了解 Python 在科学技术栈的统计分析能力,尤其是 pandas 数据处理库和 seaborn 统计可视化库。
计算管道建模:Haskell、Scala、Clojure、F#
面向对象的数据建模和面向数组的数据处理主要关注静态的数据,无论是以命名的属性形成集合的形式,还是以结构化数据形成数组的形式。
相比之下,函数式编程语言强调以计算流的形式对动态数据进行建模。即便只学习函数式编程的基本知识,也能极大地改进数据转换操作的结构,即使在其它过程式、面向对象或面向数组的程序中也是如此。
Haskell 是一种函数式编程语言,对 Python 的设计产生了重大影响,最显著的是在 Python 2.0 中引入的列表推导式。
Scala 是一种(存疑的)JVM 函数式编程语言,加上 Java、Python 和 R,它们是 Apache Spark 数据分析平台的四种主要编程语言。尽管 Scala 的设计偏重于函数式编程,但它的语法、数据模型和执行模型的设计也最大限度地降低 Java 程序员使用的门槛(因此所谓“存疑的”——其实是因为,Scala 最好被归类为一门具有强函数式编程支持的面向对象编程语言)。
Clojure 是另一种基于 JVM 的函数式编程语言,是 Lisp 的一种方言。它之所以出现在这份清单里,因为它是 Python 的 toolz 函数式编程工具包的灵感来源。
F# 不是我自己特别熟悉的语言,但它作为 .net CLR(公共语言运行时)推荐的函数式编程语言,所以还是值得关注。
学习这些语言,你可以深入了解 Python 自己的计算管道建模工具,包括容器推导式、生成器、生成器表达式、functools 和 itertools 标准库,以及第三方的 Python 函数工具包,比如 toolz。
事件驱动编程:JavaScript、Go、Erlang、Elixir
计算管道是处理数据转换和分析问题的一种极佳的方法,但许多问题需要程序作为持久性服务运行,等待事件发生,然后处理那些事件。在这类服务中,为了能够同时容纳多个用户(或多个操作),通常必须要并发地处理多个事件。
JavaScript 最初是作为 Web 浏览器的事件处理语言而开发的,允许网站开发者在本地响应客户端操作(如鼠标点击和按键敲击)和事件(如网页完成了渲染)。所有现代浏览器都支持它,它与 HTML5 领域对象模型(DOM)一起,已经成为一种定义用户界面外观和行为的事实上的标准。
Go 是谷歌设计的一种用于创建高度可伸缩的 Web 服务的专用语言,并且已经被证明是一种非常适合开发命令行应用程序的语言。从编程语言设计的角度来看,Go 最有趣的方面是在其核心并发模型中使用了通信顺序进程(Communicating Sequential Processes)概念。
Erlang 是由爱立信设计的专用语言,用于创建高度可靠的电话交换机以及类似的设备。它被用于开发出了流行的 RabbitMQ 消息代理中间件。Erlang 使用 Actor 模型作为核心的并发原语,在执行线程之间传递消息,而不是让它们直接共享数据。虽然我从未用过 Erlang 编程,但我的第一份全职工作涉及一个基于 Actor 的 C++ 并发框架,而该框架由一名前爱立信工程师开发,另外,我自己也开发了一个这样的框架,基于德州仪器(Texas Instrument)的轻量级 DSP/BIOS 运行时(现在的 TI-RTOS)里面的 TSK (Task)和 MBX (Mailbox)原语。
Elixir 出现在这份清单里,因为它被设计运行在 Erlang VM 上,提供了与 Erlang 相同的并发语义,同时还提供了一系列在语言层面上的特性,打造出一个更加全面的环境,更有可能吸引其它语言例如 Python、Java 或 Ruby 的开发者。
学习这些语言,你可以深入了解 Python 对并发和并行的支持,包括原生协程、基于生成器的协程、concurrent.futures 和 asyncio 标准库模块、第三方网络服务开发框架(如 twisted 和 Tornado)、Django 中引入的 channel 概念、GUI 框架中的事件处理循环。Python进阶
渐变类型:TypeScript
在 Python 3.5 中出现的一个比较有争议的特性是新引入的 typing 模块,它为 Python 生态带来了一个支持渐变类型的标准词典。
Python猫注:Gradual typing 是 Jeremy Siek 和 Walid Taha 在 2006 年提出的理论,允许程序中同时出现动态类型与静态类型。国内有人将其翻译为“渐进类型”、“渐近类型”、“渐进定型”、“动静混合类型”等等,但我觉得并不够好。渐变类型也许是我的首创,借鉴自 Photoshop 的渐变颜色,表达出从动态类型到静态类型的过渡(或者说交融共处的)特点。“渐变”一词有打破界限分明的状态(如大小、远近、明暗),从而达到中和状态的含义。
对于那些主要从 C、C++ 和 Java 等语言中接触静态类型的人来说,这似乎是一个令人吃惊的糟糕特性(因此引发了争议)。
微软的 TypeScript 为 Javascript 程序提供了渐变类型,因此它能更好地解释这个概念。TypeScript 代码会被编译成 JavaScript 代码(然后就不包含运行时类型检查),流行的 JavaScript 库的 TypeScript 注解会维护在专用的 DefinitelyTyped 仓中。
正如 Chris Neugebauer 在澳大利亚 PyCon 演讲 中指出的,这很像是 Python 与 typeshed 类型提示库、以及像 mypy 这种类型推断和分析工具之间的关系。
在本质上,TypeScript 和 Python 中的类型提示都是编写特定种类的测试的方式,要么使用单独的文件(就像普通测试一样),要么嵌入在代码体中(就像静态类型语言中的类型声明一样)。对于这两种情况,你都要运行一个单独的命令,来检查其余代码是否与已添加的类型断言一致(对于 TypeScript,这是在编译成 JavaScript 时隐式地发生的;对于 Python 的类型提示,这是一个完全可选的静态分析任务)。
动态元编程:Hy、Ruby
C、C++、C# 和 Java 等语言的学习者在接触 Python 时,经常感到不安的一个特性是“代码即数据”(code is data):函数和类之类的东西是运行时对象,可以像其它对象一样被操纵。
Hy 是一种 Lisp 方言,可以同时在 CPython VM 和 PyPy VM 上运行。Lisp 及其方言将“代码即数据”的概念推到了极致,因为 Lisp 代码由嵌套列表组成,这些列表描述了要执行的操作(这门语言的名称本身就代表列表处理器“LISt Processor”)。Lisp 风格语言的强大之处在于,它让你非常容易编写出自己的领域特定代码。Lisp 风格语言的最大缺点是,它让你非常容易编写出自己的领域特定代码,但这可能导致每个人写的代码变得难以阅读。
Ruby 语言在许多方面与 Python 相似,但对于 Python 中“支持但不鼓励”的动态元编程特性,Ruby 社区则相对开放。这包括在已有类定义中添加新的方法,以及使用闭包来实现语言的核心结构,例如迭代。(Python猫注:关于两种语言中迭代结构的实现对比,可阅读 这篇文章)
学习这些语言,可以让你深入了解 Python 自己的动态元编程特性,包括函数和类装饰器、猴子补丁、unittest.mock 标准库、以及像 wrapt 这样的第三方对象代理模块。(我不知道学习哪种语言可以深入了解 Python 的元类系统,如果有人在这方面有任何建议,请在评论中告知我。Python 的元类驱动着很多特性,例如核心的类型系统、抽象基类、枚举类型和渐变类型表达式的运行时求值。)
务实问题解决:Lua、PHP、Perl
主流的编程语言并不是孤立存在的——它们作为一个更大的生态系统的一部分而存在,这个生态系统由发行者(企业和社区组织)、终端用户、框架开发者、工具开发者、教育工作者等等组成。
Lua 是一种流行的编程语言,作为一种脚本引擎嵌入到大型程序中。标志性的例子是它被魔兽世界游戏用来编写客户端插件,它也被嵌入到了许多 Linux 发行版所使用的 RPM 组件中。与 CPython 相比,Lua 运行时的大小通常只有 CPython 的十分之一,而且由于较弱的自省能力,它更容易与程序的其它部分以及服务器的操作系统隔离开来。Lua 社区对 Python 生态的一个显著贡献是 LuaJIT FFI(Foreign Function Interface 外来函数接口),它被 CPython 和 PyPy 采用,作为支持 JIT 的 cffi 接口库的基础。
PHP 是另一种流行的编程语言,作为 Linux-Apache-MySQL-PHP LAMP 技术栈中的“P”而崛起,因为它专注于生成 HTML 页面,并且在早期的虚拟专用服务器(Virtual Private Server,简称 VPS) 提供商中广泛使用。尽管其设计上有诸多的概念性缺陷让人感到绝望,但它如今是几个极其流行的开源 Web 服务的基础,包括 Drupal 内容管理系统、Wordpress 博客引擎和维基百科的 MediaWiki 引擎。PHP 还支撑着一些重要的服务,比如 Ushahidi 平台,它是一个开源的社会化新闻发布社区。
像 PHP 一样,Perl 也是基于 Linux 而崛起。但跟 PHP 专门作为 Web 开发平台不同,Perl 是系统管理员的工具,在基于文本的 Linux 操作系统中,它使用正则表达式将命令的输出转成字符串,并进行操作。当 sh、awk 和 sed 都无法胜任某些任务时,Perl 出现并派上了用场。
学习这些语言,在编程语言设计方面,不大可能获得什么漂亮审美或者优雅概念。学习它们,最可能的是了解编程语言在现实中是如何被分发和采用的,以及这些在多大程度上取决于偶然的机会、历史意外事件、以及发行商在系统中默认集成而降低了使用门槛,而不是取决于语言本身固有的能力。
特别是,它可以提供对以下项目的重要性的洞察:CKAN、OpenStack NFV、Blender、SciPy、OpenMDAO、PyGMO、PyCUDA、树莓派基金会和 Python 被大量商业组织采用,以保护它们在 Python 生态中不断的投入。
编程思维:Scratch、Logo
我经常跟函数式编程以及面向对象编程的拥护者们讨论,他们声称这类语言就像过程式语言一样易于学习。
如果我们谈论的是通过嵌入式编程(例如机器人)进行教学,在软件中建模的对象都有现实世界的对应物,比如学生们可以触摸的传感器、马达和继电器,那么,那我会认为 OOP 的人有一定的道理。
但是对于其他人,我现在有一个标准的挑战:拿起一本烹饪书,把其中一个食谱翻译成你认为是容易学习的编程语言,然后找一个能理解烹饪书中语言的学生,让其按照翻译好的食谱操作。其实,他们不需要真正地操作下去——只需做一个思想实验,就足以意识到他们声称的“很容易学”是假设了多少先验知识。(我很期待看到学术研究人员在现实中做这种研究——我真的很希望看到结果)
另一种解决这个问题的方法是去学习那些实际上被用来教孩子们编程思维的语言。
其中最受欢迎的是 Scratch,它使用了拖放编程界面,让学生们操纵一个独立的图形环境,它里面的电子图形可以移动,并响应环境中的事件。像 Scratch 这样的图形环境就相当于我们用来教孩子阅读和书写的图画书。
使用特殊的教育语言来操作图形环境的想法并不新鲜,最早的例子之一是 1960 年代发明的 Logo 环境。在 Logo 中(以及类似的环境,如 Python 的 turtle 模块),你主要打交道的是一个“乌龟(turtle)”,你可以通过绘制线条来指导它移动和修改环境。这样的话,命令序列、重复和状态(例如,“起笔”、“落笔”)可以基于人们的自然直觉来使用(“想象你是那只乌龟,如果你右转 90 度会发生什么?”)
回顾并重新学习这些语言,有助于有经验的程序员放下固化的观念:它们所用的概念可以提醒我们,这些概念是我们如今认为理所当然的,但初学者们需要先学习。当这样做的时候,我们能够更好地与学生和其他初学者们相处,因为我们更有可能打开逻辑的枷锁,也不会再忽略那些有必要的学习步骤。
译者附注:以上就是全部的译文。我还翻译过不少优质的文章,分享近期的几篇如下:
1、通过 for 循环,比较 Python 与 Ruby 编程思想的差别
2、Python 官方研讨会:彻底移除 GIL 真的可行么?
4、深入 Python 解释器源码,我终于搞明白了字符串驻留的原理!